<?php

/**
 * newsfeed php class
 * provides a newsfeed html tag using jquery or the marquee tag 
 * @since 2011
 * @author Jerome Lamartiniere
 * 
 * This file is part of php-newsfeed.
 *
 * php-newsfeed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 * php-newsfeed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with php-newsfeed.  If not, see <http://www.gnu.org/licenses/>
 */

class newsfeed {
	
	/**
	 * tag rendered by jquery when true, deprecated <marquee> tag rendered when false (useful when js is disabled)
	 */
	private $_bJavascript = true;
	
	/**
	 * tag id attribute value for javascript mode
	 */
	private $_sTagId = 'newsfeed';
	
	/**
	 * tag type for javascript mode
	 */
	private $_sTagType = 'div';
	
	/**
	 * <marquee> tag attrbiutes array for non-javascript mode
	 */
	private $_aMarqueeAttributes = array ('id' => 'newsfeed', 'align' => '', 'behaviour' => 'scroll', 'bgcolor' => '', 'direction' => 'left', 'width' => '', 'loop' => '', 'scrollamount' => '2', 'scrolldelay' => '' );
	
	/**
	 * The count of white pixels between the items
	 */
	private $_iSpacePixels = 200;
	
	/**
	 * The newsfeed items
	 */
	private $_aItems = array ();
	
	/**
	 * The html, or xhtml strict (if js is enabled), tags output
	 */
	private $_sOutput = '';
	
	/**
	 * The items structure and default values
	 */
	private $_aItemStructure = array ('img' => array ('src' => '', 'width' => '16', 'height' => '16', 'style' => 'vertical-align:middle;border:0px;', 'alt' => '[icon]' ), 'span' => array ('style' => '', 'text' => '' ), 'a' => array ('href' => '', 'text' => '' ) );
	
	/**
	 * The white pixel url
	 */
	private $_sWhitePixelUrl = 'clear.gif';
	
	/**
	 * The imput xml file path
	 */
	private $_sNewsXmlFilePath = 'news.xml';
	
	/**
	 * xml file
	 */
	private $_sXml = '';
	
	/**
	 * Constructor
	 */
	public function __construct($aParams) {
		
		if (isset ( $aParams ['bJavascript'] ) && is_bool ( $aParams ['bJavascript'] )) {
			$this->_bJavascript = $aParams ['bJavascript'];
		}
		if ($this->_bJavascript) {
			$this->_aMarqueeAttributes = null;
		} elseif (isset ( $aParams ['aMarqueeAttributes'] )) {
			$this->_checkMarqueeAttributes ( $aParams ['aMarqueeAttributes'] );
		}
		
		if (isset ( $aParams ['sTagType'] ) && is_string ( $aParams ['sTagType'] )) {
			$this->_sTagId = $aParams ['sTagType'];
		}
		
		if (isset ( $aParams ['sTagId'] ) && is_string ( $aParams ['sTagId'] )) {
			$this->_sTagId = $aParams ['sTagId'];
		}
		
		if (isset ( $aParams ['sNewsXmlFilePath'] ) && is_string ( $aParams ['sNewsXmlFilePath'] )) {
			$this->_sNewsXmlFilePath = $aParams ['sNewsXmlFilePath'];
		}
		$this->_parseNewsXmlFile ();
		$this->_checkItems ( $this->_aItems );
		
		if (isset ( $aParams ['iSpacePixels'] ) && is_int ( $aParams ['iSpacePixels'] ) && $aParams ['iSpacePixels'] >= 0) {
			$this->_iSpacePixels = $aParams ['iSpacePixels'];
		}
		
		if (isset ( $aParams ['sWhitePixelUrl'] ) && is_string ( $aParams ['sWhitePixelUrl'] )) {
			$this->_sWhitePixelUrl = $aParams ['sWhitePixelUrl'];
		}
	
	}
	
	/**
	 * Returns the items and renders them if needed
	 * Set $sRenderingType to html, to get a <pre>, or console (or something else), to get a simple var_export   
	 */
	public function getItems($bRender = false, $sRenderingType = 'html') {
		
		if ($bRender) {
			if ($sRenderingType == 'html') {
				echo "\n<pre>" . var_export ( $this->_aItems, 1 ) . '</pre>';
			} else {
				var_export ( $this->_aItems );
			}
		}
		
		return $this->_aItems;
	
	}
	
	public function getXml($bRender, $sRenderingType = 'html') {
		
		if ($bRender) {
			if ($sRenderingType == 'html') {
				echo "\n<pre>" . htmlentities ( $this->_sXml ) . '</pre>';
			} else {
				echo $this->_sXml;
			}
		}
		
		return $this->_sXml;
	
	}
	
	private function _setItems($aItems) {
		
		$this->_checkItems ( $aItems );
		$this->_aItems = $aItems;
	
	}
	
	public function saveItems($aItems = array()) {
		
		if (is_array ( $aItems ) && $aItems != array ()) {
			$this->_setItems ( $aItems );
		}
		$sXml = $this->_getXMLFromArray ( $this->_aItems );
		return file_put_contents ( $this->_sNewsXmlFilePath, $sXml );
	
	}
	
	public function addItem($aItem) {
		
		$this->_checkItem ( $aItem );
		$this->_aItems [] = $aItem;
	
	}
	
	/**
	 * Generates the newsfeed tag, <marquee> or <div>, and renders them if needed
	 */
	public function getTag($bRender = true) {
		
		if ($this->_bJavascript) {
			$this->_sOutput = '<' . $this->_sTagType . ' id="' . $this->_sTagId . '">';
		} else {
			$this->_sOutput = '<marquee';
			foreach ( $this->_aMarqueeAttributes as $sAttribute => $sValue ) {
				if ($sValue != '') {
					$this->_sOutput .= ' ' . $sAttribute . '="' . $sValue . '"';
				}
			}
			$this->_sOutput .= '>';
		}
		
		foreach ( $this->_aItems as $aItem ) {
			foreach ( $aItem as $sMarkup => $aAttributes ) {
				switch ($sMarkup) {
					case 'img' :
						$this->_sOutput .= "\n" . '<img style="' . $aAttributes ['style'] . '" src="' . $aAttributes ['src'] . '" width="' . $aAttributes ['width'] . '" height="' . $aAttributes ['height'] . '" alt="' . $aAttributes ['alt'] . '" />';
						break;
					case 'span' :
						$this->_sOutput .= "\n" . '<span style="' . $aAttributes ['style'] . '">' . $aAttributes ['text'];
						break;
					case 'a' :
						$this->_sOutput .= '<img src="' . $this->_sWhitePixelUrl . '" alt="[pix]" style="width:5px;height:1px;background-image:url(\'' . $this->_sWhitePixelUrl . '\');background-repeat:repeat-x;"/><a href="' . $aAttributes ['href'] . '">' . $aAttributes ['text'] . '</a>';
						break;
				}
			}
			$this->_sOutput .= '</span>';
			$this->_sOutput .= "\n" . '<span><img src="' . $this->_sWhitePixelUrl . '" alt="[pix]" style="width:' . $this->_iSpacePixels . 'px;height:1px;background-image:url(\'' . $this->_sWhitePixelUrl . '\');background-repeat:repeat-x;"/></span>';
		}
		
		if ($this->_bJavascript) {
			$this->_sOutput .= "\n" . '</' . $this->_sTagType . '>';
		} else {
			$this->_sOutput .= "\n" . '</marquee>';
		}
		
		if ($this->_bJavascript) {
			$this->_sOutput .= '
<script type="text/javascript">
<!--
    $(function () {
        $("#' . $this->_sTagId . '").marquee("pointer").mouseover(function () {
            $(this).trigger("stop");
        }).mouseout(function () {
            $(this).trigger("start");
        }).mousemove(function (event) {
            if ($(this).data("drag") == true) {
                this.scrollLeft = $(this).data("scrollX") + ($(this).data("x") - event.clientX);
            }
        }).mousedown(function (event) {
            $(this).data("drag", true).data("x", event.clientX).data("scrollX", this.scrollLeft);
        }).mouseup(function () {
            $(this).data("drag", false);
        });
    });
//-->
</script>';
		}
		
		if ($bRender) {
			echo $this->_sOutput;
		}
		
		return $this->_sOutput;
	
	}
	
	private function _checkMarqueeAttributes($aMarqueeAttributes) {
		
		$this->_aMarqueeAttributes = array ();
		foreach ( $aMarqueeAttributes as $sAttribute => $sValue ) {
			if (! array_key_exists ( $sAttribute, $this->_aMarqueeAttributes )) {
				throw new Exception ( 'unknown marquee attribute', 10 );
			}
			$this->_aMarqueeAttributes [$sAttribute] = $sValue;
		}
		
		return true;
	
	}
	
	private function _checkItems(&$aItems = array()) {
		
		if (! is_array ( $aItems ) || $aItems == array ()) {
			throw new Exception ( 'bad items array', 14 );
		}
		foreach ( $aItems as $iKey => $aItem ) {
			foreach ( $aItem as $sTagType => $aAttributes ) {
				if (! array_key_exists ( $sTagType, $this->_aItemStructure )) {
					throw new Exception ( 'unknown attribute ' . $sTagType . ' in items structure', 15 );
				}
				foreach ( $aAttributes as $sAttribute => $sValue ) {
					if (! array_key_exists ( $sAttribute, $this->_aItemStructure [$sTagType] )) {
						throw new Exception ( 'unknow attribute ' . $sAttribute . ' for markup ' . $sTagType . ' in items structure', 16 );
					}
					if (! is_string ( $sValue )) {
						throw new Exception ( 'not string value for attribute ' . $sAttribute . ' of markup ' . $sTagType . ' in items structure', 17 );
					}
				}
				foreach ( $this->_aItemStructure [$sTagType] as $sAttribute => $sDefaultValue ) {
					if (! isset ( $aAttributes [$sAttribute] ) && $sDefaultValue != '') {
						$aItems [$iKey] [$sTagType] [$sAttribute] = $sDefaultValue;
					}
				}
			}
		}
		
		return true;
	
	}
	
	private function _checkItem($aItem) {
		
		if (! is_array ( $aItem ) || $aItem == array ()) {
			throw new Exception ( 'bad item array', 14 );
		}
		foreach ( $aItem as $sTagType => $aAttributes ) {
			if (! array_key_exists ( $sTagType, $this->_aItemStructure )) {
				throw new Exception ( 'unknown attribute ' . $sTagType . ' in items structure', 15 );
			}
			foreach ( $aAttributes as $sAttribute => $sValue ) {
				if (! array_key_exists ( $sAttribute, $this->_aItemStructure [$sTagType] )) {
					throw new Exception ( 'unknow attribute ' . $sAttribute . ' for markup ' . $sTagType . ' in items structure', 16 );
				}
				if (! is_string ( $sValue )) {
					throw new Exception ( 'not string value for attribute ' . $sAttribute . ' of markup ' . $sTagType . ' in items structure', 17 );
				}
			}
		}
		
		return true;
	
	}
	
	/**
	 * Input xml file parsing
	 */
	private function _parseNewsXmlFile() {
		
		$this->_sXml = file_get_contents ( $this->_sNewsXmlFilePath );
		if (! $this->_sXml) {
			throw new Exception ( 'xml file not found at ' . $this->_sNewsXmlFilePath, 20 );
		}
		$oXML = new DOMDocument ();
		$oXML->loadXML ( $this->_sXml );
		$oMainNode = $oXML->documentElement;
		$aNews = $this->_getArrayFromXMLNode ( $oMainNode );
		$oXML = null;
		$oMainNode = null;
		
		$this->_organizeItems ( $aNews ['item'] );
		
		return $this->_aItems;
	}
	
	/**
	 * Organizes the news to a simple array
	 */
	private function _organizeItems($aNews) {
		
		foreach ( $aNews as $iKey => $aItem ) {
			foreach ( $aItem ['tag'] as $aTag ) {
				foreach ( $aTag ['attr'] as $aAttr ) {
					$this->_aItems [$iKey] [$aTag ['type']] [$aAttr ['name']] = $aAttr ['#text'];
				}
			}
		}
	
	}
	
	/** xml to array from php.net **/
	private function _getArrayFromXMLNode($node) {
		
		$array = false;
		
		if ($node->hasAttributes ()) {
			foreach ( $node->attributes as $attr ) {
				$array [$attr->nodeName] = $attr->nodeValue;
			}
		}
		
		if ($node->hasChildNodes ()) {
			if ($node->childNodes->length == 1) {
				$array [$node->firstChild->nodeName] = $node->firstChild->nodeValue;
			} else {
				foreach ( $node->childNodes as $childNode ) {
					if ($childNode->nodeType != XML_TEXT_NODE) {
						$array [$childNode->nodeName] [] = $this->_getArrayFromXMLNode ( $childNode );
					}
				}
			}
		}
		
		return $array;
	
	}
	
	private function _getXMLFromArray($aItems) {
		
		$sXml = '<?xml version="1.0" encoding="utf-8"?>' . "\n" . '<items>' . "\n";
		foreach ( $aItems as $iKey => $aItem ) {
			$sXml .= "\t" . '<item id="' . $iKey . '">' . "\n";
			foreach ( $aItem as $sTagType => $aAttributes ) {
				$sXml .= "\t\t" . '<tag type="' . $sTagType . '">' . "\n";
				foreach ( $aAttributes as $sName => $sValue ) {
					$sXml .= "\t\t\t" . '<attr name="' . $sName . '">' . $sValue . '</attr>' . "\n";
				}
				$sXml .= "\t\t" . '</tag>' . "\n";
			}
			$sXml .= "\t" . '</item>' . "\n";
		}
		$sXml .= '</items>';
		
		return $sXml;
	
	}

}

?>